﻿#include <learn/painter/win_ctx.h>

using namespace xi;
using namespace xi::painter;

#pragma comment(lib, "gdiplus.lib")

namespace
{

std::shared_ptr<Paint_Context> globalContext;

} // namespace

// 窗口过程函数声明
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

Win_Context::Win_Context(HINSTANCE hInstance, int nCmdShow, int x, int y, int width, int height,
                       std::function<void(const Paint_Context *)> init, std::function<void(const Paint_Context *)> deal,
                       const wchar_t *title)
{
#ifndef NDEBUG
    // 在 Windows 平台上显示控制台
    AllocConsole();
    FILE *pCout;
    freopen_s(&pCout, "CONOUT$", "w", stdout);
    freopen_s(&pCout, "CONOUT$", "w", stderr);

    // 设置控制台代码页为 UTF-8
    SetConsoleOutputCP(CP_UTF8);
#endif

    // 初始化 GDI+
    Gdiplus::GdiplusStartupInput gdiplusStartupInput;
    Gdiplus::GdiplusStartup(&m_gdiplusToken, &gdiplusStartupInput, NULL);

    // 注册窗口类
    WNDCLASSEX wcex;
    wcex.cbSize = sizeof(WNDCLASSEX);
    wcex.style = CS_HREDRAW | CS_VREDRAW;
    wcex.lpfnWndProc = WndProc;
    wcex.cbClsExtra = 0;
    wcex.cbWndExtra = 0;
    wcex.hInstance = hInstance;
    wcex.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
    wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    wcex.lpszMenuName = NULL;
    wcex.lpszClassName = L"MainWindow";
    wcex.hIconSm = LoadIcon(NULL, IDI_APPLICATION);

    RegisterClassEx(&wcex);

    // 保存尺寸
    globalContext = std::make_shared<Paint_Context>(width, height, init, deal);

    // 创建窗口
    m_hWnd = CreateWindow(L"MainWindow", title, WS_OVERLAPPEDWINDOW, x, y, width + 16, height + 39, NULL, NULL,
                          hInstance, NULL);

    ShowWindow(m_hWnd, nCmdShow);
    UpdateWindow(m_hWnd);
}

int Win_Context::message_loop()
{
    // 定时器，60 帧每秒
    SetTimer(m_hWnd, 1, 1000 / 60, NULL);

    // 主消息循环
    MSG msg;
    while (GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    // 销毁定时器
    KillTimer(m_hWnd, 1);

    // 关闭 GDI+
    Gdiplus::GdiplusShutdown(m_gdiplusToken);

#ifndef NDEBUG
    // 释放控制台
    FreeConsole();
#endif
    return (int)msg.wParam;
}

// 窗口过程函数的实现
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    static bool continueRender = true;
    static bool screenshot = false;

    switch (message)
    {
    case WM_PAINT: {
        PAINTSTRUCT ps;
        HDC hdc = BeginPaint(hWnd, &ps);

        // 获得颜色数组
        if (globalContext != nullptr && continueRender)
        {
            // 使用 GDI+ 绘制 PNG 图像
            Gdiplus::Graphics graphics(hdc);
            auto image = globalContext->paint_event();
            auto bitmap = std::make_shared<Gdiplus::Bitmap>(image->width, image->height, PixelFormat32bppARGB);
            for (int i = 0; i < image->height; ++i)
            {
                for (int j = 0; j < image->width; ++j)
                {
                    auto color = image.get(j, i);
                    Gdiplus::Color gdiColor =
                        Gdiplus::Color::MakeARGB(color.bgra[3], color.bgra[2], color.bgra[1], color.bgra[0]);
                    bitmap->SetPixel(j, i, gdiColor);
                }
            }

            // 绘制位图
            graphics.DrawImage(bitmap.get(), 0, 0, bitmap->GetWidth(), bitmap->GetHeight());

            if (screenshot)
            {
                UINT num = 0;  // 图像编码器的数量
                UINT size = 0; // 图像编码器数组的字节数

                Gdiplus::GetImageEncodersSize(&num, &size); // 获得系统编码器的数量和大小
                if (size == 0)
                    return 0;

                // 获得编码器
                Gdiplus::ImageCodecInfo *pICI = new Gdiplus::ImageCodecInfo[size];
                if (pICI == NULL)
                    return 0;

                // 获取编码器信息
                CLSID encoderClsid;
                Gdiplus::GetImageEncoders(num, size, pICI);
                for (int j = 0; j < num; ++j)
                    if (wcscmp(pICI[j].MimeType, L"image/png") == 0)
                        encoderClsid = pICI[j].Clsid;

                delete[] pICI;

                // 保存位图为 PNG 图像
                std::wstring filename = TEST_DATA_PATH L"/screenshot-" + std::to_wstring(std::time(nullptr)) + L".png";
                bitmap->Save(filename.c_str(), &encoderClsid, NULL);
                screenshot = false;
            }
        }

        EndPaint(hWnd, &ps);
        break;
    }
    case WM_SIZE: {
        // 修改渲染尺寸
        RECT rect;
        GetWindowRect(hWnd, &rect);

        int width = rect.right - rect.left - 16;
        int height = rect.bottom - rect.top - 39;

        if (globalContext != nullptr)
            globalContext->resize_event(width, height);
        break;
    }
    case WM_MOUSEMOVE: {
        float x = LOWORD(lParam);
        float y = HIWORD(lParam);
        globalContext->context()->mouse_move_event(Vec2f{x, y});
        break;
    }
    case WM_MOUSEWHEEL: {
        short wheelDelta = GET_WHEEL_DELTA_WPARAM(wParam);
        globalContext->context()->mouse_wheel_event(Vec2f{0.0f, wheelDelta / 120.0f});
        break;
    }
    case WM_RBUTTONDOWN: {
        float x = LOWORD(lParam);
        float y = HIWORD(lParam);
        globalContext->context()->mouse_press_event(Vec2f{x, y}, AIS_MouseMsg::Button::Right);
        break;
    }
    case WM_LBUTTONDOWN: {
        float x = LOWORD(lParam);
        float y = HIWORD(lParam);
        globalContext->context()->mouse_press_event(Vec2f{x, y}, AIS_MouseMsg::Button::Left);
        break;
    }
    case WM_MBUTTONDOWN: {
        float x = LOWORD(lParam);
        float y = HIWORD(lParam);
        globalContext->context()->mouse_press_event(Vec2f{x, y}, AIS_MouseMsg::Button::Middle);
        break;
    }
    case WM_RBUTTONUP:
    case WM_LBUTTONUP:
    case WM_MBUTTONUP: {
        float x = LOWORD(lParam);
        float y = HIWORD(lParam);
        globalContext->context()->mouse_release_event(Vec2f{x, y});
        break;
    }
    case WM_KEYUP: {
        // wParam 中存储了按键的虚拟键码
        int key = wParam;
        switch (key)
        {
        case 'S': {
            screenshot = true;
            break;
        }
        case VK_SPACE: {
            // 切换持续渲染
            continueRender = !continueRender;
            break;
        }
        default:
            break;
        }
        break;
    }
    case WM_TIMER: {
        // 定时器事件，重新渲染图像
        InvalidateRect(hWnd, NULL, FALSE);
        break;
    }
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}
